#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include "chip8.h"

BYTE g_default_font_array[] = {
    0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
    0x20, 0x60, 0x20, 0x20, 0x70, // 1
    0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
    0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
    0x90, 0x90, 0xF0, 0x10, 0x10, // 4
    0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
    0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
    0xF0, 0x10, 0x20, 0x40, 0x40, // 7
    0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
    0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
    0xF0, 0x90, 0xF0, 0x90, 0x90, // A
    0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
    0xF0, 0x80, 0x80, 0x80, 0xF0, // C
    0xE0, 0x90, 0x90, 0x90, 0xE0, // D
    0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
    0xF0, 0x80, 0xF0, 0x80, 0x80  // F
};

BYTE g_memory[CHIP8_MEMORY_SIZE] = {};
WORD g_stack[CHIP8_STACK_SIZE] = {}; // Stack for subroutine calls
BYTE g_scr_buff[CHIP8_SCR_WIDTH][CHIP8_SCR_HEIGHT] = {};
BYTE g_v[16] = {}; // 16 registers
BYTE g_keys[16] = {}; // Key states
WORD g_i = 0;
WORD g_pc = 0;
WORD g_sp = 0;
BYTE g_delay_timer = 0;
BYTE g_sound_timer = 0;
CHIP8_SCREEN_UPDATE_CALLBACK g_screen_update_callback;
CHIP8_SOUND_CALLBACK g_sound_callback;
CHIP8_ASSEMBLY_DUMP_CALLBACK g_assembly_dump_callback;

struct timeval last_timer_update;
const long TIMER_INTERVAL_US = 1000000 / 60; // 60Hz = ~16667 microseconds

void op_0(WORD ins)
{
    switch (ins)
    {
        case 0xe0:
            if (g_screen_update_callback)
            {
                memset(g_scr_buff, 0, sizeof(g_scr_buff)); // 清除屏幕缓冲区
                (*g_screen_update_callback)(g_scr_buff);
            }
            break;
        case 0xee:
            if (g_sp >= 0) {
                g_sp--;
                g_pc = g_stack[g_sp];
            } else {
                fprintf(stderr, "Stack underflow\n");
            }
            break;
    }
}

void op_1(WORD ins)
{
    g_pc = ins & 0x0FFF;
}

void op_2(WORD ins)
{
    if (g_sp < CHIP8_STACK_SIZE) {
        g_stack[g_sp++] = g_pc;
        g_pc = ins & 0x0FFF;
    } else {
        fprintf(stderr, "Stack overflow\n");
    }
}

void op_3(WORD ins)
{
    BYTE x = (ins & 0x0F00) >> 8;
    BYTE kk = ins & 0x00FF;
    if (g_v[x] == kk) {
        g_pc += 2;
    }
}

void op_4(WORD ins)
{
    BYTE x = (ins & 0x0F00) >> 8;
    BYTE kk = ins & 0x00FF;
    if (g_v[x] != kk) {
        g_pc += 2;
    }
}

void op_5(WORD ins)
{
    BYTE x = (ins & 0x0F00) >> 8;
    BYTE y = (ins & 0x00F0) >> 4;
    if (g_v[x] == g_v[y]) {
        g_pc += 2;
    }
}

void op_6(WORD ins)
{
    BYTE x = (ins & 0x0F00) >> 8;
    BYTE kk = ins & 0x00FF;
    g_v[x] = kk;
}

void op_7(WORD ins)
{
    BYTE x = (ins & 0x0F00) >> 8;
    BYTE kk = ins & 0x00FF;
    g_v[x] += kk;
}

void op_8(WORD ins)
{
    BYTE x = (ins & 0x0F00) >> 8;
    BYTE y = (ins & 0x00F0) >> 4;
    switch (ins & 0x000F)
    {
        case 0x0:
            g_v[x] = g_v[y];
            break;
        case 0x1:
            g_v[x] |= g_v[y];
            break;
        case 0x2:
            g_v[x] &= g_v[y];
            break;
        case 0x3:
            g_v[x] ^= g_v[y];
            break;
        case 0x4:
            int s = g_v[x] + g_v[y];
            g_v[0xF] = s > 0xFF ? 1 : 0;
            g_v[x] = s & 0xFF;
            break;
        case 0x5:
            g_v[0xF] = g_v[x] >= g_v[y] ? 1 : 0;
            g_v[x] -= g_v[y];
            break;
        case 0x6:
            g_v[0xF] = g_v[x] & 1;
            g_v[x] >>= 1;
            break;
        case 0x7:
            g_v[0xF] = g_v[x] > g_v[y] ? 0 : 1;
            g_v[x] = g_v[y] - g_v[x];
            break;
        case 0xE:
            g_v[0xF] = g_v[x] >> 7;
            g_v[x] <<= 1;
            break;
    }
}

void op_9(WORD ins)
{
    BYTE x = (ins & 0x0F00) >> 8;
    BYTE y = (ins & 0x00F0) >> 4;
    if (g_v[x] != g_v[y]) {
        g_pc += 2;
    }
}

void op_A(WORD ins)
{
    g_i = ins & 0x0FFF;
}

void op_B(WORD ins)
{
    g_pc = (ins & 0x0FFF) + g_v[0];
}

void op_C(WORD ins)
{
    BYTE x = (ins & 0x0F00) >> 8;
    BYTE kk = ins & 0x00FF;
    g_v[x] = (rand() % 256) & kk;
}

void op_D(WORD ins)
{
    BYTE x = (ins & 0x0F00) >> 8;
    BYTE y = (ins & 0x00F0) >> 4;
    BYTE height = ins & 0x000F;

    g_v[0xF] = 0;
    for (BYTE row = 0; row < height; row++) {
        BYTE y_pos = (g_v[y] + row) % CHIP8_SCR_HEIGHT;
        BYTE pixel = g_memory[g_i + row];
        for (BYTE col = 0; col < 8; col++) {
            BYTE x_pos = (g_v[x] + col) % CHIP8_SCR_WIDTH;
            BYTE pixel_value = (pixel & (0x80 >> col)) ? 1 : 0;
            BYTE scr_pixel_value = g_scr_buff[x_pos][y_pos];
            if (pixel_value && scr_pixel_value) {
                    g_v[0xF] = 1;
            }

            g_scr_buff[x_pos][y_pos] = pixel_value ^ scr_pixel_value;
            (*g_screen_update_callback)(g_scr_buff);
        }
    }
}

void op_E(WORD ins)
{
    BYTE x = (ins & 0x0F00) >> 8;
    switch (ins & 0x00FF)
    {
        case 0x9E: // Skip next instruction if key in Vx is pressed
            if (g_keys[g_v[x]] != 0) {
                g_pc += 2;
            }
            break;
        case 0xA1: // Skip next instruction if key in Vx is not pressed
            if (g_keys[g_v[x]] == 0) {
                g_pc += 2;
            }
            break;
    }
}

void op_F(WORD ins)
{
    BYTE x = (ins & 0x0F00) >> 8;
    switch (ins & 0x00FF)
    {
        case 0x07: // Set Vx to delay timer value
            g_v[x] = g_delay_timer;
            break;
        case 0x0A: // Wait for a key press and store it in Vx
            BYTE pressed_keys, pressed_idx;
            for (int idx = 0; idx < 16; idx++) {
                if (g_keys[idx]) {
                    pressed_keys++;
                    pressed_idx = idx;
                }
            }
            switch (pressed_keys) {
                case 0:
                    g_pc -= 2;
                    break;
                case 1:
                    g_v[x] = pressed_idx;
                    break;
            }
            break;
        case 0x15: // Set delay timer to Vx
            g_delay_timer = g_v[x];
            break;
        case 0x18: // Set sound timer to Vx
            g_sound_timer = g_v[x];
            break;
        case 0x1E: // Add Vx to I
            g_i += g_v[x];
            break;
        case 0x29: // Set I to the location of the sprite for digit Vx
            BYTE d = g_v[x];
            g_i = CHIP8_FONT_OFFSET + (d * 5);
            break;
        case 0x33: // Store BCD representation of Vx in memory at I, I+1, I+2
            BYTE val = g_v[x];
            g_memory[g_i + 2] = val % 10;
            val /= 10;
            g_memory[g_i + 1] = val % 10;
            val /= 10;
            g_memory[g_i] = val % 10;
            break;
        case 0x55: // Store registers V0 to Vx in memory starting at I
            for (BYTE i = 0; i <= x; i++) {
                g_memory[g_i + i] = g_v[i];
            }
            break;
        case 0x65: // Read registers V0 to Vx from memory starting at I
            for (BYTE i = 0; i <= x; i++) {
                g_v[i] = g_memory[g_i + i];
            }
            break;
    }
}

CHIP8_INSTRUCTION_CALLBACK g_ins_cbs[16] = {
    op_0, op_1, op_2, op_3, op_4, op_5, op_6, op_7,
    op_8, op_9, op_A, op_B, op_C, op_D, op_E, op_F
};

void decode_ins(const WORD ins, char* inst_rep)
{
    BYTE opcode = (ins & 0xF000) >> 12;
    switch (opcode)
    {
        case 0:
            switch (ins & 0xFF)
            {
                case 0xE0:
                    strcpy(inst_rep, "00E0");
                    break;
                case 0xEE:
                    strcpy(inst_rep, "0xEE");
                    break;
                default:
                    strcpy(inst_rep, "0NNN");
                    break;
            }
            break;
        case 1:
            strcpy(inst_rep, "1NNN");
            break;
        case 2:
            strcpy(inst_rep, "2NNN");
            break;
        case 3:
            strcpy(inst_rep, "3XNN");
            break;
        case 4:
            strcpy(inst_rep, "4XNN");
            break;
        case 5:
            strcpy(inst_rep, "5XY0");
            break;
        case 6:
            strcpy(inst_rep, "6XNN");
            break;
        case 7:
            strcpy(inst_rep, "7XNN");
            break;
        case 8:
            switch (ins & 0x000F)
            {
                case 0x0:
                    strcpy(inst_rep, "8XY0");
                    break;
                case 0x1:
                    strcpy(inst_rep, "8XY1");
                    break;
                case 0x2:
                    strcpy(inst_rep, "8XY2");
                    break;
                case 0x3:
                    strcpy(inst_rep, "8XY3");
                    break;
                case 0x4:
                    strcpy(inst_rep, "8XY4");
                    break;
                case 0x5:
                    strcpy(inst_rep, "8XY5");
                    break;
                case 0x6:
                    strcpy(inst_rep, "8XY6");
                    break;
                case 0x7:
                    strcpy(inst_rep, "8XY7");
                    break;
                case 0xE:
                    strcpy(inst_rep, "8XYE");
                    break;
            }
            break;
        case 9:
            strcpy(inst_rep, "9XY0");
            break;
        case 0xA:
            strcpy(inst_rep, "ANNN");
            break;
        case 0xB:
            strcpy(inst_rep, "BNNN");
            break;
        case 0xC:
            strcpy(inst_rep, "CXNN");
            break;
        case 0xD:
            strcpy(inst_rep, "DXYN");
            break;
        case 0xE:
            switch (ins & 0x00FF)
            {
                case 0x9E:
                    strcpy(inst_rep, "EX9E");
                    break;
                case 0xA1:
                    strcpy(inst_rep, "EXA1");
                    break;
            }
            break;
        case 0xF:
            switch (ins & 0x00FF)
            {
                case 0x07:
                    strcpy(inst_rep, "FX07");
                    break;
                case 0x0A:
                    strcpy(inst_rep, "FX0A");
                    break;
                case 0x15:
                    strcpy(inst_rep, "FX15");
                    break;
                case 0x18:
                    strcpy(inst_rep, "FX18");
                    break;
                case 0x1E:
                    strcpy(inst_rep, "FX1E");
                    break;
                case 0x29:
                    strcpy(inst_rep, "FX29");
                    break;
                case 0x33:
                    strcpy(inst_rep, "FX33");
                    break;
                case 0x55:
                    strcpy(inst_rep, "FX55");
                    break;
                case 0x65:
                    strcpy(inst_rep, "FX65");
                    break;
            }
            break;
    }
}

int chip8_init(const char* rom, 
               CHIP8_SCREEN_UPDATE_CALLBACK screen_update_callback,
               CHIP8_SOUND_CALLBACK sound_callback,
               CHIP8_ASSEMBLY_DUMP_CALLBACK assembly_dump_callback)
{
    srand((unsigned int)time(NULL));

    // Initialize the Chip-8 emulator state
    g_pc = CHIP8_PRG_OFFSET;
    g_i = 0;
    g_sp = 0;
    g_delay_timer = 0;
    g_sound_timer = 0;

    // Load the font table into memory
    for (int i = 0; i < sizeof(g_default_font_array); i++) {
        g_memory[CHIP8_FONT_OFFSET + i] = g_default_font_array[i];
    }

    // Load the ROM into memory
    FILE* file = fopen(rom, "rb");
    if (!file) {
        perror("Failed to open ROM\n");
        return RESULT_FAIL;
    }

    // Get the length of the ROM file
    fseek(file, 0, SEEK_END);
    long rom_size = ftell(file);
    fseek(file, 0, SEEK_SET);
    if (rom_size > CHIP8_RESERVED_OFFSET - CHIP8_PRG_OFFSET + 1) {
        fclose(file);
        fprintf(stderr, "ROM size exceeds available memory\n");
        return RESULT_FAIL;
    }

    fread(&g_memory[CHIP8_PRG_OFFSET], 1, rom_size, file);
    fclose(file);

    gettimeofday(&last_timer_update, NULL);

    g_screen_update_callback = screen_update_callback;
    g_sound_callback = sound_callback;
    g_assembly_dump_callback = assembly_dump_callback;

    return RESULT_OK;
}

void chip8_cycle()
{
    // get the instruction code
    WORD ins = (g_memory[g_pc] << 8) | g_memory[g_pc + 1];

    // dump the assembly code if the dump callback is set
    if (g_assembly_dump_callback) {
        char inst_rep[5] = {0};
        decode_ins(ins, inst_rep);
        (*g_assembly_dump_callback)(g_pc, ins, inst_rep);
    }

    // get the opcode
    BYTE opcode = (ins & 0xF000) >> 12;
    g_pc += 2;

    // execute the instruction
    CHIP8_INSTRUCTION_CALLBACK ins_callback = g_ins_cbs[opcode];
    (*ins_callback)(ins);

    // get the current time
    struct timeval current_time;
    gettimeofday(&current_time, NULL);

    // calculate elapsed time
    long elapsed_us = (current_time.tv_sec - last_timer_update.tv_sec) * 1000000L +
                      (current_time.tv_usec - last_timer_update.tv_usec);

    // if 1/60 second has passed, update the timers
    if (elapsed_us >= TIMER_INTERVAL_US) {
        if (g_delay_timer > 0) {
            g_delay_timer--;
        }

        if (g_sound_timer > 0) {
            if (g_sound_callback) {
                (*g_sound_callback)();
            }
            g_sound_timer--;
        }

        last_timer_update = current_time;
    }
}
